package com.xw.unification.interceptor;

import com.xw.unification.config.WebUnificationMvcProperties;
import com.xw.unification.enums.UnifiedRequestInterceptorStep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author qiwei
 */
public class UnifiedRequestInterceptorChain implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(UnifiedRequestInterceptorChain.class);

    private static final ThreadLocal<UnifiedRequestInterceptorChainContext> CHAIN_CONTEXT =
            ThreadLocal.withInitial(() -> new UnifiedRequestInterceptorChainContext().init(0));

    private final WebUnificationMvcProperties properties;

    private ApplicationContext ctx;

    private final List<UnifiedRequestInterceptor> interceptors = new ArrayList<>();

    public UnifiedRequestInterceptorChain(WebUnificationMvcProperties properties) {
        this.properties = properties;
        initInterceptors();
    }

    protected UnifiedRequestInterceptorChain() {
        this.properties = null;
        initInterceptors();
    }

    public void setApplicationContext(ApplicationContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        CHAIN_CONTEXT.get().init(0);
        return execute(UnifiedRequestInterceptorStep.PRE_HANDLE, interceptor -> {
                    if (handler instanceof HandlerMethod) {
                        return interceptor.preHandle(request, response, (HandlerMethod) handler, ctx, properties);
                    } else {
                        return true;
                    }
                });
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        execute(UnifiedRequestInterceptorStep.POST_HANDLE, interceptor -> {
            if (handler instanceof HandlerMethod) {
                interceptor.postHandle(request, response, (HandlerMethod) handler, ctx, properties, modelAndView);
            }
            return true;
        });
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        execute(UnifiedRequestInterceptorStep.AFTER_COMPLETION, interceptor -> {
            if (handler instanceof HandlerMethod) {
                interceptor.afterCompletion(request, response, (HandlerMethod) handler, ctx, properties, ex);
            }
            return true;
        });
    }

    public void registerInterceptor(UnifiedRequestInterceptor interceptor) {
        if (interceptor != null) {
            interceptors.add(interceptor);
        }
    }

    public int getInterceptorCounts() {
        return interceptors.size();
    }

    static UnifiedRequestInterceptorChainContext getChainContext() {
        UnifiedRequestInterceptorChainContext ctx = Optional.ofNullable(CHAIN_CONTEXT.get())
                .orElse(new UnifiedRequestInterceptorChainContext().init(0));
        CHAIN_CONTEXT.remove();
        return ctx;
    }

    private boolean execute(UnifiedRequestInterceptorStep step, ExceptedPredicate<UnifiedRequestInterceptor> predictor) throws Exception {
        UnifiedRequestInterceptorChainContext chainContext = CHAIN_CONTEXT.get();
        UnifiedRequestInterceptor interceptor;
        boolean asc = step == UnifiedRequestInterceptorStep.PRE_HANDLE;
        int plus = asc ? 1 : -1;
        for (int i = asc ? 0 : (interceptors.size() - 1); i < interceptors.size() && i >= 0; i += plus) {
            interceptor = interceptors.get(i);
            try {
                if (interceptor == null) {
                    chainContext.setNowIndex(i);
                    continue;
                }
                if (!predictor.test(interceptor)) {
                    chainContext.addErrorNode(i, interceptor, step, null);
                    return false;
                }
                chainContext.setNowIndex(i);
            } catch (Exception e) {
                chainContext.addErrorNode(i, interceptor, step, e);
                throw e;
            }
        }
        return true;
    }

    protected void initInterceptors() {
    }

    interface ExceptedPredicate<T> {
        boolean test(T t) throws Exception;
    }

}
